using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.Phone.Shell;
using WindowsPhoneEssentials.ComponentModel;
using WindowsPhoneEssentials.Expressions;
using WindowsPhoneEssentials.Storage;
using WindowsPhoneEssentials.Tasks;
using WindowsPhoneMVC.Navigation;
using WindowsPhoneMVC.Navigation.Interfaces;
using WindowsPhoneMVC.Phone.Interfaces;

namespace WindowsPhoneMVC
{
    public class ViewModelBase : NotifyPropertyChanged, IActivationAware
    {
        private readonly IList<PropertyInfo> transientProperties = new List<PropertyInfo>();
        private readonly IList<PropertyInfo> applicationTransientProperties = new List<PropertyInfo>();
        private readonly List<Action> cleanupTasks = new List<Action>();
        private readonly List<Action> activateTasks = new List<Action>();
        private bool activated;

        public INavigator Navigator { get; internal set; }
        public ITaskManager TaskManager { get; internal set; }
        public IPageTransientStore PageState { get; internal set; }
        public ITransientStore ApplicationState { get; internal set; }

        protected void Transient<T>(Expression<Func<T>> propertySelector, params Type[] knownTypes)
        {
            transientProperties.Add(PropertyResolver.PropertyFor(propertySelector));
        }

        protected void ApplicationTransient<T>(Expression<Func<T>> propertySelector, params Type[] knownTypes)
        {
            applicationTransientProperties.Add(PropertyResolver.PropertyFor(propertySelector));
        }

        public void Activated(object sender, ActivatedEventArgs args)
        {
            foreach (var property in transientProperties)
                HydrateProperty(property, PageState);
            foreach (var property in applicationTransientProperties)
                HydrateProperty(property, ApplicationState);

            foreach (var activateTask in activateTasks)
                    activateTask();

            activated = true;
            OnActivated();
        }

        public void Deactivated(object sender, DeactivatedEventArgs args)
        {
            foreach (var property in transientProperties)
                PersistProperty(property, PageState);
            foreach (var property in applicationTransientProperties)
                PersistProperty(property, ApplicationState);

            foreach (var cleanupTask in cleanupTasks)
                cleanupTask();

            OnDeactivated();
        }

        protected void HydrateProperty(PropertyInfo property, ITransientStore storageProvider)
        {
            var propertyKey = string.Format("{0}.{1}", GetType().FullName, property.Name);
            if (!PageState.Contains(propertyKey)) return;

            var value = PageState.Load<TransientProperty>(propertyKey);
            property.SetValue(this, value.Value, null);
        }

        protected void PersistProperty(PropertyInfo property, ITransientStore storageProvider)
        {
            var propertyKey = string.Format("{0}.{1}", GetType().FullName, property.Name);
            var value = property.GetValue(this, null);
            PageState.Save(propertyKey, new TransientProperty{ Value = value });
        }

        /// <summary>
        /// Invoked when the viewmodel has been activated by the phone
        /// </summary>
        public virtual void OnActivated()
        {
        }

        /// <summary>
        /// Invoked when the viewmodel is about to be deactivated
        /// </summary>
        public virtual void OnDeactivated()
        {
            
        }

        public ControllerActions<TController> Controller<TController>()
            where TController : Controller
        {
            return new ControllerActions<TController>(this);
        }

        internal void AddSetupTask(Action action)
        {
            if (activated)
                action();
            activateTasks.Add(action);
        }

        internal void AddCleanupTask(Action action)
        {
            cleanupTasks.Add(action);
        }
    }
}